-
-
Notifications
You must be signed in to change notification settings - Fork 96
Sequence equality #249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sequence equality #249
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lots of good fixes in here. I think there are some introduced bugs though. I tried to pick them out.
Found and fixed a couple more issues while I scanned everything. Lmk if I should pull them out.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly a rehash of our slack discussion, but also called out some other concerns.
@@ -878,88 +878,88 @@ namespace jank::runtime | |||
case 1: | |||
return dynamic_call(source, s->first()); | |||
case 2: | |||
return dynamic_call(source, s->first(), s->next_in_place()->first()); | |||
return dynamic_call(source, s->first(), (s = s->next_in_place())->first()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a local TODO that notes that persistent_list_sequence
shouldn't exist, since persistent_list
itself is a sequence.
This change is a big red flag, since this is an extremely hot code path. I don't want to impact its perf for this one case which needs to change anyway.
If you want to add a comment here noting that it doesn't work for persistent_list
, that's ok. If you want to remove persistent_list_sequence
, that's also ok, but let's do it in a separate PR.
Either way, let's please undo this file's changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way, let's please undo this file's changes.
Sure.
I don't want to impact its perf for this one case which needs to change anyway.
I don't think the bug is reachable right now.
persistent_list::next_in_place
is the potential problem, but it's currently unreachable to apply_to
since a fresh seq is taken and then persistent_list_sequence::next_in_place
is called instead.
I'm not sure what would happen if persistent_list_sequence
were removed and (presumably) replaced with persistent_list
itself. I think the apply_to
bug would be introduced if persistent_list::next_in_place
continued to allocate a new list, as is implied in this comment.
persistent_list_sequence_ptr persistent_list::next_in_place() const
{
/* In-place updates don't make sense for lists, since any call to fresh_seq would return
* a list sequence. So we know, principally, that a list itself cannot be considered fresh. */
return next();
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment would no longer apply, if we removed persistent_list_sequence
. This would be updated to actually mutate the list.
else if(!rhs) | ||
if(!rhs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why remove the else
? I find it helps with the logical flow. Also, by having the else if
, your assertion is proven by the compiler to be redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added back the else
. Since the compiler pointed out the assertion was redundant, does that mean returning false
is equivalent to returning !lhs
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, equivalent. Was just like that for symmetry.
assert(it != nil::nil_const()); | ||
assert(seq != nil::nil_const()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's impossible for these assertions to be false, proven by the type system. I think you're concerned about the next_in_place(it)
call somehow returning an object_ptr
. But it could never do that, since it would result in a compiler error.
Now, the history is that next_in_place
has been in flux for a year or so and I was making some changes which would allow for it to be optional. That never happened, but it's why we're not using it->next_in_place()
here. Ultimately, it's doing the same thing and all of these can be changed back. I just haven't done that yet, since it's a perf change and I haven't been focusing on perf.
Either way, your concerns about this being nil should not be keeping you up. auto it(fresh_seq());
means that we'll have a fully typed it
for the currently visited object. We cannot assign an object_ptr
to it. C++ won't allow it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. What about something like this loop? Can this be nil
?
jank/compiler+runtime/src/cpp/clojure/core_native.cpp
Lines 575 to 581 in bf75688
fn->arity_3 = [](object * const l, object * const r, object * const rest) -> object * { | |
if(!is_equiv(l, r)) | |
{ | |
return obj::boolean::false_const(); | |
} | |
for(auto it(fresh_seq(rest)); it != nullptr; it = next_in_place(it)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That one can be nil
, yes, since it's using the object_ptr
overloads. However, it cannot be nullptr
, since those functions never return nullptr
.
@@ -16,6 +16,7 @@ namespace jank::runtime::obj | |||
: value{ value } | |||
, count{ count } | |||
{ | |||
assert(0 < to_int(count)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels like this could be an exception, rather than assertion, since someone embedding jank may create it a repeat with 0
and we don't need to kill the whole program.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
It doesn't necessarily need to be an error either. We can made these constructors robust by adding checks in its members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All good with your previous changes. The new bug fix has a comment, though. I'll fix it locally.
Thanks for the iteration, Ambrose!
@@ -578,7 +578,8 @@ jank_object_ptr jank_load_clojure_core_native() | |||
return obj::boolean::false_const(); | |||
} | |||
|
|||
for(auto it(fresh_seq(rest)); it != nullptr; it = next_in_place(it)) | |||
for(auto it(fresh_seq(rest)); it != nullptr && it != obj::nil::nil_const(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, here, it
cannot be nullptr
. That's not part of the contract. Everything returned from runtime::
fns should be non-null.
The one exception is the next_in_place
and fresh_seq
templates. Ultimately, we can remove those (not in this PR); they're only there for a historical partial refactor which never panned out.
There should never be a case, right now, where we need to check for nullptr and nil.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should never be a case, right now, where we need to check for nullptr and nil.
There's a few more in multi_function.cpp
.
Closes #243
Closes #250
Closes #251
Closes #274
Closes #283
It would be nice to replace
nullptr
returns withnil
insequenceable
but it enables important optimizations for calls tosequenceable
member functions. I did not investigate returningoption
types.